/*
 * main.cc
 * Main loop
 *
 *  Created on: Oct 16, 2014
 *      Author: andy
 */

#define _USE_MATH_DEFINES
#include <math.h>
#include <iostream>
#include <SFML/Graphics.hpp>
#include "Radio.h"
#include "calculation.h"

// Global SFML font to use. This is global so that we only have to load it
// once and can use it anywhere.
sf::Font font;

/* updateDisplay(..)
 * Render the display from the given inputs and calculation results.
 */
void updateDisplay(sf::RenderWindow &window,
		           const char* station_ident,
		           int station_radial,
		           int desired_radial,
		           bool signal_valid,
		           CalcResults &results) {

	// Center position in the window, and radius of the compass ring.
	const int CENTER_X = 275;
	const int CENTER_Y = 275;
	const int RADIUS = 250;

	// Clear the window to black
	window.clear(sf::Color::Black);

	// Results invalid? then draw the not-valid indicator.
	// Note that we also draw the indicator if something is wrong with the
	// internal state of the calculation results, or if any of the inputs
	// is out-of-bounds.
	if(desired_radial < 0 || desired_radial > 360 ||
	   station_radial < 0 || station_radial > 360 ||
	   !results.areResultsValid() ||
	   !results.valid ||
	   results.direction == CalcResults::UNKNOWN) {

		// Draw the compass ring, but in red
		sf::CircleShape shape(RADIUS, 100);
		shape.setPosition(CENTER_X - RADIUS, CENTER_Y - RADIUS);
		shape.setFillColor(sf::Color::Black);
		shape.setOutlineThickness(10);
		shape.setOutlineColor(sf::Color::Red);
	    window.draw(shape);

		// Draw the station identifier
		sf::Text station;
		station.setFont(font);
		station.setCharacterSize(20);
		station.setColor(sf::Color::Red);
		station.setString(station_ident);
		sf::FloatRect bounds = station.getLocalBounds(); // Draw text centered
		station.setOrigin(bounds.left + bounds.width/2.0f, bounds.top + bounds.height/2.0f);
		station.setPosition(CENTER_X, 412);
		window.draw(station);

	    // Draw the text "Signal Invalid" centered and in red.
		sf::Text invalid;
		invalid.setFont(font);
		invalid.setCharacterSize(30);
		invalid.setColor(sf::Color::Red);
		invalid.setString("Signal Invalid");
		bounds = invalid.getLocalBounds(); // Draw text centered
		invalid.setOrigin(bounds.left + bounds.width/2.0f, bounds.top + bounds.height/2.0f);
		invalid.setPosition(CENTER_X, CENTER_Y);
		window.draw(invalid);

	}
	else {

		// Else, draw the normal indicator

		// ---------------------------------------------------------------
		// Draw static elements
		// ---------------------------------------------------------------

		// Draw the compass ring
		sf::CircleShape ring(RADIUS, 100);
		ring.setPosition(CENTER_X - RADIUS, CENTER_Y - RADIUS);
		ring.setFillColor(sf::Color::Black);
		ring.setOutlineThickness(10);
		ring.setOutlineColor(sf::Color::White);
		window.draw(ring);

		// Draw the deflection points indicators
		sf::CircleShape deflection;
		deflection.setRadius(4);
		deflection.setOrigin(4,4); // Centered
		for (int i = -8; i <= 8; i += 2)
		{
			// Deflection pips are vertically centered, spaced 25 px apart.
			float x = CENTER_X + i * 20;
			deflection.setPosition(x, CENTER_Y);
			window.draw(deflection);
		}

		// Draw the station identifier
		sf::Text station;
		station.setFont(font);
		station.setCharacterSize(20);
		station.setColor(sf::Color::Yellow);
		station.setString(station_ident);
		sf::FloatRect bounds = station.getLocalBounds(); // Draw text centered
		station.setOrigin(bounds.left + bounds.width/2.0f, bounds.top + bounds.height/2.0f);
		station.setPosition(CENTER_X, 412);
		window.draw(station);

		// ---------------------------------------------------------------
		// Draw dynamic elements
		// ---------------------------------------------------------------

		// Draw the compass degree pips (every 22.5 degrees)
		sf::CircleShape pip;
		sf::Text news[4];

		pip.setRadius(4);
		pip.setFillColor(sf::Color::Black);
		pip.setOrigin(4,4); // Centered

		news[0].setString("N");
		news[1].setString("E");
		news[2].setString("S");
		news[3].setString("W");

		for(int i = 0; i < 4; ++i) {
			news[i].setFont(font);
			news[i].setCharacterSize(12);
			news[i].setColor(sf::Color::Black);

			// Adjust origin so that the text is centered
			sf::FloatRect bounds = news[i].getLocalBounds();
			news[i].setOrigin(bounds.left + bounds.width/2.0f, bounds.top + bounds.height/2.0f);

		}

		for(int i = 0; i < 12; i++) {
			// Translate the pip into the proper position
			float deg = i * 30.0f - desired_radial - 90;
			float x = CENTER_X + (RADIUS+5) * cos(2 * M_PI * deg / 360.0f);
			float y = CENTER_Y + (RADIUS+5) * sin(2 * M_PI * deg / 360.0f);

			// Draw the NEWS pips as text
			if(i % 3 == 0) {
				news[i/3].setPosition(x,y);
				window.draw(news[i/3]);
			}
			else {
				// Draw everything else as just dots
				pip.setPosition(x,y);
				window.draw(pip);
			}
		}

		// Draw the deflection needle
		// Clamp the result displacement to +-10 degrees
		float displacement = results.displacement < -10 ? -10 :
							 results.displacement >  10 ?  10 :
							 results.displacement;

		// Convert the raw displacement into the angle to rotate the displacement
		// needle by so that it will intersect the correct displacement marker.
		displacement = 360 * atan(20 * displacement / 280.0) / (2 * M_PI);

		sf::RectangleShape needle(sf::Vector2f(300, 6));
		needle.setOrigin(3, 3);
		//setting attributes of the CDI
		needle.setPosition(CENTER_X, 50);
		needle.rotate(90 - displacement);
		window.draw(needle);

		// Draw the to/from indicator
		sf::Text direction;
		direction.setFont(font);
		direction.setCharacterSize(20);
		direction.setColor(results.direction == CalcResults::TO ? sf::Color::Green : sf::Color::Blue);
		direction.setString(results.direction == CalcResults::TO ? "To" : "From");
		bounds = direction.getLocalBounds(); // Draw text centered
		direction.setOrigin(bounds.left + bounds.width/2.0f, bounds.top + bounds.height/2.0f);
		direction.setPosition(412, 138);
		window.draw(direction);

	}

    // Display the newly rendered output
    window.display();
}

/* printDebugOutput(..)
 * Display the internal parameters (input radial, station values, and
 * calculation results) on the console, for debugging
 */
void printDebugOutput(const char* station_ident,
        int station_radial,
        int desired_radial,
        bool signal_valid,
        CalcResults &results) {

	// Print input parameters: station signal valid, station radial, desired radial.
	if(!signal_valid)
		std::cout << "Station signal not valid.\n";
	else
		std::cout << "Station radial: " << station_radial << std::endl;
	std::cout << "Desired radial: " << desired_radial << std::endl;

	// Print calculation results.
	if(!results.valid) {
		// Input signal was invalid
		std::cout << "Calculation results not valid." << std::endl;
	}
	else if(results.direction == CalcResults::UNKNOWN) {
		// Input signal was valid, but direction is unknown.
		std::cout << "Calculation direction unknown, results not valid." << std::endl;
	}
	else {
		// Results and direction valid, print everything.

		std::cout << "Displacement: " << results.displacement << ", ";

		std::cout << "Direction: ";
		if(results.direction == CalcResults::TO)
			std::cout << "TO\n";
		else if(results.direction == CalcResults::FROM)
			std::cout << "FROM\n";
		else
			std::cout << "UNKNOWN\n";
	}
}

/* main()
 * DO EVERYTHING.
 */
int main(void) {

	// Construct and initialize radio
	Radio r;

	// SFML main window
	sf::RenderWindow window(sf::VideoMode(550, 550), "VOR", sf::Style::Close);

	// Limit framerate, so we don't use too much CPU redrawing everything.
	window.setFramerateLimit(30);

	// Load default font
	// Note that if the font cannot be loaded for any reason, the result will be
	// for the to/from indicator to not be displayed. Everything else will
	// continue to work normally.
	font.loadFromFile("SourceSansPro-Regular.otf");

	/* Main loop:
	 * 1. Handle input (updates desired radial)
	 * 2. Read from radio
	 * 3. Perform calculation
	 * 4. Update display
	 */

	// Read radio state. Since there's no way to "re-tune" the radio at
	// run-time, we read these values once before starting the main loop.

	const char *station_ident = r.getIdent();   // Station identifier
	bool signal_valid = r.isValid();            // Station signal is valid?
	int station_radial = r.getRadial();         // Station radial

	// User inputs and calculation results.
	int desired_radial = 0;                     // Desired (input) radial
	bool input_changed = true;                  // Has input radial changed since last render?
	CalcResults results;                        // Calculation results

	// In debug builds, output the station ident and parameters on startup.
#ifdef DEBUG
	std::cout << "Station identifier: " << station_ident << std::endl;
	if(!signal_valid)
		std::cout << "Station signal not valid.\n";
	else
		std::cout << "Station radial: " << station_radial << std::endl;
#endif

	// -------------------------------------------------------------------------
	// Main event/rendering loop
	// -------------------------------------------------------------------------
	while(window.isOpen()) {

		// Process pending events
		sf::Event event;
		while (window.pollEvent(event))
		{
			switch(event.type) {

				// Window closed? then actually close the window and quit.
				case sf::Event::Closed:
					window.close();
					break;

				// Mouse wheel scroll? Rotate desired radial.
				case sf::Event::MouseWheelMoved:
					desired_radial += event.mouseWheel.delta;                // Shift radial by scroll amount
					desired_radial = ((desired_radial % 360) + 360) % 360;   // Wrap to [0,360)
					input_changed = true;                                    // Set flag for recalculation/rendering
					break;

				// Key pressed? 'd' prints debugging info.
				case sf::Event::TextEntered:
					if(event.text.unicode == 'd')
						printDebugOutput(station_ident,station_radial,desired_radial,signal_valid,results);
					break;

				// We don't care about any other events.
				default:
					break;
			}

		}

		// Perform calculation and update display (only if input changed)
		if(input_changed) {
			signal_valid = calculate(station_radial, desired_radial, signal_valid, results);

			// Update display
			updateDisplay(window,
						  station_ident,
						  station_radial,
						  desired_radial,
						  signal_valid,
						  results);

			input_changed = false;
		}

	}

	return 0;
}






